Práctica: Actividad Evaluable 3
Asignatura: Data Driven Security
Fecha: 27-01-2025
| Grupo 1 |
|---|
| Javier Gómez Rodríguez |
| Fernando Palma Villanueva |
| Mireia Náger Piazuelo |
Primero se cargan los logs del servidor, se analizan y se preparan los datos para el análisis posterior, algunas de las acciones que se han realizado son:
Así, descubrimos que se trata de las peticiones HTTP que se realizan sobre un servidor; cada observación en el dataframe incluye la siguiente información:
Finalmente obtenemos el dataframe df, que contiene los siguientes valores:
datatable(df)
Para obtener el número único de usuarios se parte de la premisa que los usuarios se corresponden con el origen de la petición (Path) y que los errores son aquellas respuestas (StatusCode) en los rangos 400 y 500.
A continuación se muestra el código ejecutado, que nos da el número de usuarios únicos que hay por cada tipo de error diferente.
# Clasificación de usuarios
resumen_usuarios <- df %>%
group_by(Path) %>%
reframe(
StatusCode = unique(as.numeric(as.character(StatusCode))[as.numeric(as.character(StatusCode)) >= 400])
)
resultado_final <- resumen_usuarios %>%
group_by(StatusCode) %>%
reframe(
num_usuarios = n())
El resultado es:
print(resultado_final)
## # A tibble: 5 × 2
## StatusCode num_usuarios
## <dbl> <int>
## 1 400 1
## 2 403 5
## 3 404 152
## 4 500 29
## 5 501 11
Si lo que se quiere es mostrar los usuarios que no han tenido ningún error, se puede calcular con el siguiente código:
#Primero se localizan los usuarios que tienen error
usuarios_con_error <- df %>% filter(as.numeric(as.character(StatusCode)) >= 400)
#Se imprime el número único de usuarios con error
print(paste("Usuarios con error: ", length(unique(usuarios_con_error$Path))))
## [1] "Usuarios con error: 192"
#Luego se eliminan los usuarios con error del dataframe
usuarios_sin_error <- df %>% anti_join(usuarios_con_error, by="Path")
#Se imprime el número único de usuarios sin error
print(paste("Usuarios sin error: ", length(unique(usuarios_sin_error$Path))))
## [1] "Usuarios sin error: 2141"
Se analizan los distintos tipos de petición (GET, POST, PUT, DELETE) que se encuentran en la columna RequestType para identificar la frecuencia de cada una de éstas. Se muestra la tabla resultante a continuación indicando el tipo de petición y el número de peticiones para cada tipo.
# Obtener la tabla de frecuencias de la columna RequestType
tabla_frecuencias <- table(df$RequestType)
# Mostrar la tabla de frecuencias
print(tabla_frecuencias)
##
## GET HEAD POST
## 46020 106 1622
A continuación se muestra la tabla resultante de mostrar la misma información pero para seleccionando los recursos de tipo imagen (i.e. jpg, png, gif, bmp y svg)
#Repetir el análisis, esta vez filtrando previamente aquellas peticiones correspondientes a recursos ofrecidos de tipo imagen.
df_imagenes <- df %>%
filter(ResourceType %in% c("jpg", "jpeg", "png", "gif", "bmp", "svg"))
tabla_imagenes <- table(df_imagenes$RequestType)
print(tabla_imagenes)
##
## GET HEAD POST
## 22157 56 206
Para generar los dos gráficos de esta pregunta se han filtrado los valores del df para que solo aparezcan los valores de tamaño menores a 25000, así se puede analizar mejor los datos visualmente.
El primer gráfico es scattered para analizar los tamaños de descarga en función del tiempo, el gráfico parece indicar que:
#Se eliminan los tamaños superiores a 25K que pueden desviar la información del gráfico
df_filtered_size <- df %>% filter(Size <25000)
# Scatterplot para comparar fecha y tamaño
ggplot(df_filtered_size, aes(x=Date, y=Size) ) +
geom_point(alpha=0.01) +
labs(title = "Tamaño de las descargas por fecha", x="Fecha", y="Descargas") +
theme_minimal()
El segundo gráfico muestra la densidad del tamaño de ficheros descargados y ahí se confirma que la gran mayoría de peticiones son cercanos a 0; se vuelven a observar los dos picos alrededor de 2500 y 5000.
# Se añade un gráfico de densidad que muestra la distribución del tamaño de la descarga
ggplot(df_filtered_size, aes(x=Size)) +
geom_density(fill="lightblue", color="lightgrey", alpha=0.8) +
labs(title = "Densidad de las descargas", x="Tamaño", y="Densidad") +
theme_minimal()
Se utiliza la función geom_histogram para generar un gráfico que muestre el número de peticiones servidas durante el tiempo, agrupadas por hora.
# 7. Generar un gráfico que permita visualizar el número de peticiones servidas a lo largo del tiempo.
table_TimeVsRequest <- table(df$Date, df$RequestType)
df_TimeVsRequest <- as.data.frame(table_TimeVsRequest)
colnames(df_TimeVsRequest) <- c("Time", "RequestType", "Request")
df_TimeVsRequest$Time <- as.POSIXct(df_TimeVsRequest$Time, format="%Y-%m-%d %H:%M:%S") # Se convierte la columna time en formato de fecha y hora
# Crear la gráfica
ggplot(df_TimeVsRequest, aes(x = Time)) +
geom_histogram(binwidth = (3600), fill = "lightblue", color = "blue") +
scale_x_datetime(date_breaks = "2 hours", date_labels = "%Y-%m-%d %H:%M") +
labs(title = "Histograma de Fechas por Hora",
x = "Fecha y Hora",
y = "Frecuencia") +
theme_minimal() +
theme(axis.text.x = element_text(angle = 45, hjust = 1)) +
geom_text(stat = "bin", aes(label = after_stat(count)), binwidth = (3600), hjust= -0.2 , angle = 90)
En este caso, utilizamos el algoritmo k-means para segmentar las peticiones en función de dos variables relevantes, la “Fecha numérica” (que corresponde a la hora de la petición) y el “Tamaño de la respuesta” (en bytes); además, se elije un valor de k de 3 y otro de 6 para el ejercicio.
A continuación se muestra el código que llama a la función k-means; como la tarea de limpieza de datos se ha realizado en la fase 1, aquí solo quedaría pendiente seleccionar del dataframe los valores númericos y llamar a la función one_hot para que transforme los factores.
A continuación se muestra el código:
# Seleccionamos las columnas númericas y factores
df_kmeans <- df[, c("NumericDate", "RequestType","ResourceType","ResourceLength","Protocol","Version","StatusCode","Size")]
#Convertimos los factores a números eliminando NA
df_kmeans_one_hot <- one_hot(as.data.table(df_kmeans), sparsifyNAs = TRUE)
# Llamamos a la función kmeans
set.seed(2025)
# Se aplica el algoritmo k-means con 3 y 6 clusters, con 25 iteraciones
resultado_kmeans_3 <- kmeans(df_kmeans_one_hot, centers = 3, iter.max=1000, nstart = 25)
resultado_kmeans_6 <- kmeans(df_kmeans_one_hot, centers = 6, iter.max=1000, nstart = 25)
# Se agregan los clusters al dataframe con los valores númericos
df_kmeans_one_hot$Cluster3 <- as.factor(resultado_kmeans_3$cluster)
df_kmeans_one_hot$Cluster6 <- as.factor(resultado_kmeans_6$cluster)
Con 3 clústers: La segmentación nos produce 3 patrones generales:
Con 6 clústers: al dividir las peticiones en más grupos, se produce una segmentación más detalla, con la que somos capaces de detectar picos de actividad de menor envergadura y variaciones en el tamaño de las respuestas que mediante una menor cantidad de clústers serían difíciles de observar.
Utilizar 3 clústers nos da una información general pautas más amplias (por ejemplo, solicitudes grandes frente a pequeñas) mientras que con 6 clústers seremos capaces de llegar a una segmentación más adecuada, a la captación de variaciones más sutiles, y a una información más detallada de las peticiones.
La aplicación de k-means con diferentes k nos permite capturar la información relativa a las pautas generales y al mismo tiempo variaciones de las peticiones si usamos 3 clústers para la información más general y hasta 6 clústers para la más concreta, lo que nos parece esencial para mejorar la comprensión de las peticiones en conjuntos de datos de gran tamaño.
# Se crean los dos gráficos
ggplot(df_kmeans_one_hot,aes(x = NumericDate, y = ResourceLength, color = Cluster3)) +
geom_point(size = 1, alpha = 0.05) +
labs(title = "K-Means con 3 subgrupos",
x = "Fecha Númerica",
y = "Longitud del recurso",
color = "Cluster") +
theme_minimal()
ggplot(df_kmeans_one_hot, aes(x = NumericDate, y = ResourceLength, color = Cluster6)) +
geom_point(size = 1, alpha = 0.05) +
labs(title = "K-Means con 6 subgrupos",
x = "Fecha Númerica",
y = "Longitud del recurso",
color = "Cluster") +
theme_minimal()